# This file is part of the AutoMAT distribution (https://bitbucket.com/mahomaho/AutoMAT).
# Copyright (c) 2020 Mattias Holmqvist.
# 
# AutoMAT is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
# 
# AutoMAT is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
# GNU General Public License for more details.
# 
# You should have received a copy of the GNU General Public License
# along with AutoMAT.  If not, see <https://www.gnu.org/licenses/>.

from collections import defaultdict
import functools
import inspect
import math
import types, sys
import bisect

import lxml.etree as ET

from .support import *

AR_NS='{http://autosar.org/schema/r4.0}'
AR={None : 'http://autosar.org/schema/r4.0'}
def no_ns(tag):
	return tag.replace(AR_NS,'')

def findtext(element, path):
	if path == '.':
		return element.text
	try:
		for t in path.split('/'):
			element=next(element.iterchildren(AR_NS+t))
		return element.text
	except:
		return ''

class ContainerBase(object):
	_atpMixed=False
	_xml=()
	_parent=None
	_binding=None
	def _setup(self):
		self._instances.append(self)
	def _unsetup(self):
		self._instances.remove(self)
	def _setxml(self,xml,binding,parent):
		self._parent=parent
		if xml.text is None:
			xml.text=''
		self._valueType._validate(xml.text)
		self._xml=(xml,)
		self._binding=binding
	@classmethod
	def _sortxml(cls, xml):
		return
	def _appendxml(self,other):
		if self._xml[0].text!=other._xml[0].text:
			print(f'Error: Merging simple content at {self.__repr__()} where value differs {self._xml[0].text}:{self._xml[0].base} != {other._xml[0].text}:{other._xml[0].base}')
		if self._xml[0].attrib!=other._xml[0].attrib:
			print(f'Error: Merging simple content at {self.__repr__()} where value differs {self._xml[0].attrib}:{self._xml[0].base} != {other._xml[0].attrib}:{other._xml[0].base}')
		self._xml=(*self._xml,other._xml[0])
	def _merge(self,other,splitkey=None):
		if self._xml[0].text!=other._xml[0].text:
			print(f'Error: Merging simple content at {self.__repr__()} where value differs {self._xml[0].text}:{self._xml[0].base} != {other._xml[0].text}:{other._xml[0].base}')
		if self._xml[0].attrib!=other._xml[0].attrib:
			print(f'Error: Merging simple content at {self.__repr__()} where value differs {self._xml[0].attrib}:{self._xml[0].base} != {other._xml[0].attrib}:{other._xml[0].base}')
		self._xml=(*self._xml,other._xml[0])

	def __iter__(self):
		return singleiterator(self)
	def __getitem__(self,key):
		from .autosar_r4p0 import Group
		if isinstance(key,int):
			assert key==0 or key==-1,f'{self.__repr__()} is not an array'
			return self
		assert isinstance(self,Group.Referrable),f'Trying to index {self.__repr__()} with {key} which is not possible'
		assert self.shortName.val()==key,f'Cannot index {self.__repr__()} with {key}'
		return self
	def __len__(self):
		return 1
	def __bool__(self):
		return True
	def editor(self,file=None):
		from . import edit
		from . import files
		if file is not None:
			file=files.get(file)
		return edit.Editor(self,file)
	def aggregations(self):
		return ()
	def __str__(self):
		return self.__repr__()[7:].replace('.','/')
	def __repr__(self):
		names=[]
		cont=self
		from .autosar_r4p0 import Group
		while cont._parent is not ModelNone:
			if isinstance(cont,Group.Referrable):
				while True:
					shortname = cont.shortName.val()
					if not shortname:
						try:
							# try to read shortName in xml file if read point is before the shortName is read interpreted from xml file
							shortname=next(cont._xml[0].iterchildren('{http://autosar.org/schema/r4.0}SHORT-NAME')).text
						except:
							# unnamed container, refer to it with the non shortName based name instead
							break
					names.append(shortname)
					cont = cont._parent
					if not isinstance(cont,Group.Referrable):
						if cont._parent is ModelNone:
							names.append('autosar')
							return '.'.join(reversed(names))
						cont = cont._parent
			child=cont._parent.__dict__[cont._binding._childname]
			if child is cont:
				names.append(cont._binding._name)
			else:
				names.append(cont._binding._name+'['+str(child.index(cont))+']')
			cont=cont._parent
		names.append('autosar')
		return '.'.join(reversed(names))
	def parent(self):
		return self._parent
	def file(self):
		def generator():
			for xml in self._xml:
				yield xml.base
		return ModelList(generator)

	def file_line(self):
		def generator():
			for xml in self._xml:
				if xml.base:
					if xml.sourceline is not None:
						yield f'{xml.base}:{xml.sourceline}'
					else:
						yield f'{xml.base}'
		return ModelList(generator)

	def model(self):
		return self
	def attributes(self):
		return sorted(attrib._name for attrib in self._attribs)
	def _getAggregation(self,aggregation):
		name='_Aggregation'+aggregation._childname
		if name not in self.__dict__:
			self.__dict__[name]=Aggregation(self,aggregation)
		return self.__dict__[name]
	def binding(self):
		return self._parent._getAggregation(self._binding)
	def validate(self):
		for v in getattr(self, '_validator', ()):
			v(self)
	def __dir__(self):
		ret=[]
		for d in object.__dir__(self):
			if d[0]!='_' and getattr(self,d):
				ret.append(d)
		return ret

class ValueTypeBase():
	def _xmlstr(self):
		try:
			return self._xml[0].text
		except:
			return ''
	def val(self):
		ret=self._xml[0].text
		assert ret is not None
		#if ret is None:
		#	return ''
		return ret

class ModelReference(ValueTypeBase):
	"""This primitive denotes a name based reference. For detailed syntax see the xsd.pattern.

* first slash (relative or absolute reference) [optional]
* Identifier  [required]
* a sequence of slashes and Identifiers [optional]

This primitive is used by the meta-model tools to create the references."""
	from .autosar_r4p0 import SimpleTypes as _SimpleTypes
	_valueType=_SimpleTypes.Ref
	_basetype=str
	_ref=ModelNone
	_missingReferences=defaultdict(list)
	def _setup(self):
		super()._setup()
		from . import autosar
		ref=self.val()
		try:
			if ref[0]=='/':
				self._ref=functools.reduce(getattr,ref.split('/')[1:],autosar)
				assert self._ref # make sure that the ref existed
				self._ref._references.append(self)
			else:
				#todo
				self._missingReferences[ref.rsplit('/',1)[-1]].append(self)
		except:
			self._missingReferences[ref if ref[0]=='/' else ref.rsplit('/',1)[-1]].append(self)
	def _unsetup(self):
		super()._unsetup()
		if self._ref:
			self._ref._references.remove(self)
			self._ref=ModelNone
		else:
			ref=self.val()
			key=ref if ref[0]=='/' else ref.rsplit('/',1)[-1]
			refs=self._missingReferences[key]
			if refs.__len__() == 1:
				del self._missingReferences[key]
			else:
				refs.remove(self)
	def _valuepath(self):
		"get absolute path to reference if possible, else throw exeption"
		cont=self._xmlstr()
		if cont[0]!='/':
			return cont.rsplit('.',1)[-1]
		return 'autosar'+cont.replace('/','.')
	def validate(self):
		from . import autosar_r4p0
		assert self._ref,f'Referred model ({self.val()}) not loaded'
		assert self.DEST.value in self._dests,f'Specified DEST {self.DEST.value} not valid'
		assert isinstance(self._ref, self._dests[self.DEST.value]), f'referred model not of specified type {self.DEST.value}'
		super().validate()
	def ref(self):
		return self._ref

class EnumTypeBase(ValueTypeBase):
	_basetype=str
	def val(self):
		#try:
			return self._valueType(self._xml[0].text)
		#except:
			#return self._valueType.values()[0]

class StringTypeBase(ValueTypeBase):
	_basetype=str

class IntegerTypeBase(ValueTypeBase):
	_basetype=int
	def val(self):
		return int(self._xml[0].text,0)

class BooleanTypeBase(ValueTypeBase):
	_basetype=int
	def val(self):
		x=self._xml[0].text
		if x in ('true','1'):
			return True
		assert x in ('false','0')
		return False

class FloatTypeBase(ValueTypeBase):
	_basetype=float
	def val(self):
		try:
			return float(self._xml[0].text)
		except:
			raise

class NumericalTypeBase(ValueTypeBase):
	def val(self):
		return self._valueType(self._xml[0].text)
	@property
	def _basetype(self):
		try:
			return self.val()._valtype
		except:
			return float

class ValArrayTypeBase(ValueTypeBase):
	from . import simplebase as _simplebase
	_basetype = _simplebase.SimpleArrayElement
	def val(self):
		x=self._xml[0]
		def generator(xml):
			for child in xml:
				if child.tag == '{http://autosar.org/schema/r4.0}VG':
					yield [x for x in generator(child)]
				elif child.tag == '{http://autosar.org/schema/r4.0}VT':
					yield "".join(child.itertext())
				elif child.tag == '{http://autosar.org/schema/r4.0}VF':
					t = "".join(child.itertext())
					try:
						yield int(t,0)
					except ValueError as e:
						yield float(t)
				elif child.tag == '{http://autosar.org/schema/r4.0}V':
					try:
						yield int(child.text,0)
					except ValueError as e:
						yield float(child.text)
				elif child.tag == '{http://autosar.org/schema/r4.0}VTF':
					vf = list(xml.iterchildren('{http://autosar.org/schema/r4.0}VF'))
					vt = list(xml.iterchildren('{http://autosar.org/schema/r4.0}VT'))
					if vf:
						if len(vf) > 1:
							raise AssertionError('Invalid data content')
						if vt:
							raise AssertionError('Invalid data content')
						t = "".join(vf[0].itertext())
						try:
							yield int(t,0)
						except ValueError as e:
							yield float(t)
					elif vt:
						if len(vt) > 1:
							raise AssertionError('Invalid data content')
						yield "".join(vt[0].itertext())
					else: 
						pass
						raise AssertionError('Invalid data content')
		vals = [child for child in generator(x)]
		return self._basetype(vals)
		
	def _setxml(self,xml,binding,parent):
		super()._setxml(xml,binding,parent)
		self.val()
	def _set(self,selectedFile,value):
		val = self._valueType(value)
		for child in self.children()[:]:
			child.editor(selectedFile).delete()
		def setter(model, val):
			for subval in val:
				if isinstance(subval, (float,int)):
					model.v.append(subval)
				elif isinstance(subval, str):
					model.vt.append(subval)
				else:
					vg = getattr(model,'vg').append()
					setter(vg,subval)
		setter(self.editor(selectedFile), val)
	@classmethod
	def _create(cls, parent,element,file,val=None,setup=True):
		obj = super()._create(parent,element,file,None,setup)
		if val is not None:
			obj._set(file,val)
		return obj

		

class GroupBase(object):
	@classmethod
	def instances(cls):
		if issubclass(cls,ContainerBase):
			return cls._instances[:]
		ret=[]
		for subclass in cls.__subclasses__():
			if issubclass(subclass,ContainerBase):
				ret.extend(subclass._instances)
		return ret

class Aggregations(ModelList):
	def __init__(self,parent):
		self._parent=parent
		def generator():
			for e in self._parent._aggregations:
				yield self._parent._getAggregation(e)
		super().__init__(generator)
	def __getattr__(self, val):
		e=getattr(self._parent.__class__,val)
		return Aggregation(self._parent,e)
	def __dir__(self):
		return [e._name for e in self._parent._aggregations]
	def __repr__(self):
		return repr(self._parent)+'.aggregations()'

class EcucAggregations(ModelList):
	def __init__(self,parent):
		from . import autosar_r4p0
		self._parent=parent
		self._definition = self._parent.definition.ref()
		if isinstance(self._definition, autosar_r4p0.EcucChoiceContainerDef):
			def generator():
				for e in self._definition.choice:
					yield EcucAggregation(self._parent,e)
		elif isinstance(self._definition, autosar_r4p0.EcucModuleDef):
			def generator():
				for e in self._definition.container:
					yield EcucAggregation(self._parent,e)
		elif isinstance(self._definition, autosar_r4p0.EcucParamConfContainerDef):
			def generator():
				for e in self._definition.subContainer + self._definition.parameter + self._definition.reference:
					yield EcucAggregation(self._parent,e)
		else:
			def generator():
				return
				# must have a yield to make sure that function becomes a generator
				yield
		super().__init__(generator)
	def __getattr__(self, val):
		from . import autosar_r4p0
		e=getattr(self._definition,val)
		assert isinstance(e, autosar_r4p0.Group.EcucDefinitionElement), f'{val} is not a valid aggregation to {self._parent}'
		return EcucAggregation(self._parent,e)
	def __dir__(self):
		from . import autosar_r4p0
		if isinstance(self._definition, autosar_r4p0.EcucChoiceContainerDef):
			return [e.shortName.val() for e in self._definition.choice]
		elif isinstance(self._definition, autosar_r4p0.EcucModuleDef):
			return [e.shortName.val() for e in self._definition.container]
		elif isinstance(self._definition, autosar_r4p0.EcucParamConfContainerDef):
			return [e.shortName.val() for e in self._definition.subContainer + self._definition.parameter + self._definition.reference]
		else:
			return []
	def __repr__(self):
		return repr(self._parent)+'.ecucAggregations()'

class ComplexTypeBase(ContainerBase):
	def children(self):
		def generator():
			for a in self._aggregations:
				elist=getattr(self,a._name)
				if isinstance(elist, ModelList):
					for e in elist:
						yield e
				elif elist:
					yield elist
		return ModelList(generator)
	def _setup(self):
		self._instances.append(self)
		for element,_ in self._xmlchildren.values():
			me=self
			while not isinstance(me,element._parenttype):
				me=me._parent
			element._setup(me)
	def _unsetup(self):
		self._instances.remove(self)
		for element,_ in self._xmlchildren.values():
			me=self
			while not isinstance(me,element._parenttype):
				me=me._parent
			element._unsetup(me)
	@classmethod
	def _sortkey(cls, xml):
		try:
			return cls._xmlchildren[no_ns(xml.tag)][1]
		except:
			n=xml.getnext()
			if n is None:
				return 0xFFFFFFFF
			else:
				return index(n)
	@classmethod
	def _sortxml(cls,xml):
		if not cls._atpMixed:
			prevkey = 0
			extra = None
			for child in xml:
				if child.__class__ is not ET._Element:
					extra = child
					continue
				try:
					element, key = cls._xmlchildren[no_ns(child.tag)]
				except:
					xml.remove(child)
					if extra is not None:
						xml.remove(extra)
						extra = None
					continue
				if isinstance(element._desttype,dict):
					if len(child) == 0:
						xml.remove(child)
						extra = None
						continue
					element._sortxml(child)
				else:
					element._desttype._sortxml(child)
				if key >= prevkey:
					prevkey = key
					continue
				c = child.getprevious()
				ckey = prevkey
				while key < ckey:
					cnext = c
					c = c.getprevious()
					if c is None:
						break
					ckey = cls._xmlchildren.get(no_ns(c.tag),(None, ckey))[1]
				if extra is not None:
					xml.remove(extra)
					cnext.addprevious(extra)
					extra = None
				xml.remove(child)
				cnext.addprevious(child)
	def _setxml(self,xml,binding,parent):
		self._xml=(xml,)
		self._binding=binding
		self._parent=parent
		for child in xml:
			if child.__class__ is not ET._Element or not child.tag.startswith('{http://autosar.org/schema/r4.0}'):
				continue
			tag=child.tag[len('{http://autosar.org/schema/r4.0}'):]
			try:
				element,_=self._xmlchildren[tag]
			except KeyError:
				# ignore unexpected children
				continue
			me=self
			while not isinstance(me,element._parenttype):
				me=me._parent
			element._setxml(me,child)
	def _appendxml(self,other):
		self._xml=(*self._xml,other._xml[0])
		for element,_ in self._xmlchildren.values():
			me=self
			that=other
			while not isinstance(me,element._parenttype):
				me=me._parent
				that=that._parent
			element._appendxml(me,that)
	def _merge(self,other,splitkey=None):
		if splitkey is not None:
			splitelements= tuple(key.split('/')[0] for key in splitkey)
		else:
			splitelements=()
		self._xml=(*self._xml,other._xml[0])
		for element,_ in self._xmlchildren.values():
			me=self
			that=other
			while not isinstance(me,element._parenttype):
				me=me._parent
				that=that._parent
			if element._xmlname in splitelements:
				element._appendxml(me,that)
			else:
				element._merge(me,that)
	def aggregations(self):
		return Aggregations(self)

class MixedStringBase(ComplexTypeBase):
	def mixedstring(self):
		def generator():
			text=self._xml[0].text
			yield text.strip() if text else ''
			for child in self.children():
				yield child
				tail=child._xml[0].tail
				yield tail.strip() if tail else ''
		return ModelList(generator)
	@classmethod
	def _sortxml(cls, xml):
		return

class MixedBase(ComplexTypeBase):
	#def __dir__(self):
	#	ret=super().__dir__()
		#todo: add all subtypes
	#	return ret
	#def __getattr__(self,name):
		# todo: return filtered list of all containers
	#	raise AttributeError()
	@classmethod
	def _sortxml(cls, xml):
		return

class InstanceRefBase(ComplexTypeBase):
	def val(self):
		return str(self.iref())
	def iref(self):
		children=self.children()
		context = {}
		target = None
		for aggregation in reversed(self.aggregations()):
			if issubclass(aggregation.desttype(), ModelReference):
				assert aggregation.upperMultiplicity() == 1, f"Error in AutoMAT: instance ref {self} is invalid"
				target = aggregation[0].ref()
				break
		if not target:
			return ModelNone
		for child in children:
			if isinstance(child, ModelReference):
				ref = child.ref()
				if not ref:
					return ModelNone
				if ref is not target and  hasattr(ref, '_isOfType'):
					s = ref.__dict__[ref._isOfType].ref().__repr__()
					context[s] = ref
		if not context:
			# iref without valid target or context set
			return ModelNone
		top = target.__repr__()
		sortedcontext = []
		while context:
			for s, ref in context.items():
				if top.startswith(s):
					context.pop(s)
					top = ref.__repr__()
					sortedcontext.append(ref)
					break
			else:
				return ModelNone
				# iref with context that do not link together
		sortedcontext.reverse()
		return InstanceRef(sortedcontext,target)

class PrototypeBase(ComplexTypeBase):
	def isOfType(self):
		return InstanceRef([self,],self.__dict__[self._isOfType].ref())

class EcucValueTypeBase(ComplexTypeBase):
	def __bool__(self):
		return self.value.__bool__()
	def _xmlstr(self):
		try:
			return str(self.value._xml[0].text)
		except:
			return ''
	def val(self):
		try:
			return self._valueType(self.value._xml[0].text)
		except:
			#todo, should never get here, container should not exist if value is invalid
			try:
				return self.definition.ref().defaultValue.val()
			except:
				return ModelNone
	@property
	def _valueType(self):
		from . import autosar_r4p0
		#try:
		deftype=type(self.definition.ref())
		#except:
		#	return ModelNone
		if deftype is autosar_r4p0.EcucBooleanParamDef:
			return autosar_r4p0.SimpleTypes.Boolean
		elif deftype is autosar_r4p0.EcucIntegerParamDef:
			return autosar_r4p0.SimpleTypes.Integer
		elif deftype is autosar_r4p0.EcucFloatParamDef:
			return autosar_r4p0.SimpleTypes.Float
		elif deftype in (autosar_r4p0.EcucEnumerationParamDef,autosar_r4p0.EcucFunctionNameDef,autosar_r4p0.EcucLinkerSymbolDef,autosar_r4p0.EcucMultilineStringParamDef,autosar_r4p0.EcucStringParamDef):
			return autosar_r4p0.SimpleTypes.VerbatimString
		elif issubclass(deftype,autosar_r4p0.Group.EcucAbstractReferenceDef):
			return autosar_r4p0.SimpleTypes.Ref
		else:
			raise AssertionError('Automat error')

	@property
	def _basetype(self):
		from . import autosar_r4p0
		try:
			deftype=type(self.definition.ref())
		except:
			return ModelNone
		if deftype is autosar_r4p0.EcucBooleanParamDef:
			return bool
		elif deftype is autosar_r4p0.EcucIntegerParamDef:
			return int
		elif deftype is autosar_r4p0.EcucFloatParamDef:
			return float
		elif deftype in (autosar_r4p0.EcucEnumerationParamDef,autosar_r4p0.EcucFunctionNameDef,autosar_r4p0.EcucLinkerSymbolDef,autosar_r4p0.EcucMultilineStringParamDef,autosar_r4p0.EcucStringParamDef):
			return str
		elif issubclass(deftype,autosar_r4p0.Group.EcucAbstractReferenceDef):
			return str
		else:
			raise AssertionError('Automat error')
	
class InstanceRef():
	def __init__(self,contexts,target):
		self._contexts=contexts
		self._target=target
	def isOfType(self):
		contexts=self._contexts[:]
		contexts.append(self._target)
		return InstanceRef(contexts,self._target.__dict__[self._target._isOfType].ref())
	def context(self):
		if len(self._contexts) > 1:
			contexts = self._contexts[:-1]
			return InstanceRef(contexts, self._contexts[-1])
		else:
			return self._contexts[0]
	def __dir__(self):
		ret = list(self._target.__dir__())
		ret.append('context')
		return ret
	def __repr__(self):
		contexts=list(self._contexts)
		contexts.reverse()
		ret=self._target.__repr__()
		for context in contexts:
			targetpath=context.__repr__()
			root=context.__dict__[context._isOfType].ref().__repr__()
			ret=targetpath + '.isOfType()'+ret.removeprefix(root)
		return ret
	def __getattr__(self,name):
		from . import autosar_r4p0
		ret=getattr(self._target,name)
		if isinstance(ret,autosar_r4p0.Group.ARObject):
			return InstanceRef(self._contexts[:],ret)
		elif isinstance(ret,ElementList):
			def generator():
				for child in ret:
					yield InstanceRef(self._contexts[:],child)
			return InstanceRefList(generator, self, ret._element)
		return ret
	def __eq__(self, other):
		if isinstance(other, str):
			other = eval(other)
		if isinstance(other, InstanceRef):
			if self._target != other._target:
				return False
			for this, theirs in zip(self._contexts, other._contexts):
				if this != theirs:
					return False
			return True
		return False
	def __hash__(self):
		return 0x48564 ^ hash(self._target) ^ hash(tuple(self._contexts))

class Aggregation(ModelList):
	_selected=None
	@property
	def __doc__(self):
		return self._element.__doc__
	def upperMultiplicity(self):
		return self._element._numInstancesMax
	def lowerMultiplicity(self):
		return self._element._numInstancesMin
	def name(self):
		return self._element._name
	def parent(self):
		return self._instance
	def sortable(self):
		if self.upperMultiplicity()==1:
			return False
		return self._element._isOrdered
	def isProperty(self):
		return self._element._property
	def desttype(self):
		return self._element._desttype if not isinstance(self._element._desttype,dict) else list(self._element._desttype.values())
	def __dir__(self):
		ret=list(e for e in object.__dir__(self) if e[0]!='_')
		#if self._selected is None and isinstance(self._element._desttype,dict):
		#	ret.extend(e.__name__ for e in self._element._desttype.values())
		return ret
	def __repr__(self):
		ret=self._instance.__repr__()+'.'+self.name()
		if self._selected!=None:
			ret+='.'+self._selected
		return ret+super().__repr__()
	def __str__(self):
		ret = self._instance.__repr__()[7:].replace('.','/') +'/'+self.name()
		if self._selected != None:
			ret += '/' + self._selected
		return ret
	def __hash__(self):
		return hash(self._instance) ^ hash(self._element)
	def __init__(self,instance,element):
		self._instance=instance
		self._element=element
		def generator():
			elist=getattr(self._instance,self._element._name)
			if isinstance(elist, ModelList):
				if self._selected is None:
					for e in elist:
						yield e
				else:
					for e in elist:
						if type(e).__name__ == self._selected:
							yield e
			elif elist:
				yield elist
		super().__init__(generator)
		if element._property:
			if not isinstance(element._desttype,type):
				desttype=next(iter(self._element._desttype.values()))
			else:
				desttype=self._element._desttype
			if issubclass(desttype,ModelReference):
				def values():
					ret=set()
					for group in desttype._dests.values():
						ret.update(group.instances())
					return [str(e) for e in ret]
				self.values=values
			elif hasattr(desttype._valueType,'values'):
				def values(self):
					return desttype._valueType.values()
				self.values=types.MethodType(values, self)
			elif(hasattr(desttype._valueType,'_regex')):
				def regex(self):
					return desttype._valueType._regex
				self.regex=types.MethodType(regex, self)
			def valuetype(self):
				return desttype._valueType
			self.valuetype = types.MethodType(valuetype, self)
	def __getattr__(self,val):
		raise AttributeError()
		#if self._selected is None and isinstance(self._element._desttype,dict) and val in (e.__name__ for e in self._element._desttype.values()):
		#	ret=Aggregation(self._instance,self._element)
		#	ret._selected=val
		#	return ret
		#return super().__getattr__(val)
	def validate(self):
		if not self._instance._atpMixed:
			mult=len(self)
			assert self.lowerMultiplicity()<=mult, 'multiplicity low'
			assert mult<=self.upperMultiplicity(), 'multiplicity to high'
		rule=getattr(self._instance, '_validate_'+self.name(),None)
		if rule:
			rule(self)

class EcucAggregation(Aggregation):
	@property
	def __doc__(self):
		return self._definition.desc.l2[0].val()
	def upperMultiplicity(self):
		ret=math.inf if self._definition.upperMultiplicityInfinite.val() else self._definition.upperMultiplicity.val()
		return 1 if ret==None else ret
	def lowerMultiplicity(self):
		ret=self._definition.lowerMultiplicity.val()
		return 0 if ret==None else ret
	def name(self):
		return self._definition.shortName.val()
	def definition(self):
		return self._definition
	def parent(self):
		return self._instance
	def sortable(self):
		return self._definition.requiresIndex.val()
	def isProperty(self):
		from . import autosar_r4p0
		return isinstance(self._definition,(autosar_r4p0.Group.EcucParameterDef,autosar_r4p0.Group.EcucAbstractReferenceDef))
	def desttype(self):
		return self._definition.shortName.val()
	def __eq__(self, other):
		try:
			return self._definition is other._definition
		except:
			return False
	def _values_general(self):
		from . import autosar_r4p0
		ret=[]
		#fixa: finns redan metod, och returnerqa objekt istallet for strangar
		try:
			dests=self._definition.destination.ref()
			if not dests:
				return ret
			if isinstance(dests, ModelList):
				dests=list(dests)
			else:
				dests=[dests]
			for dest in dests[:]:
				path=[dest.shortName.val()]
				module=dest.parent()
				while type(module) is not autosar_r4p0.EcucModuleDef:
					path.append(module.shortName.val())
					module=module.parent()
				refined=module.references().parent().select.findall(lambda e: type(e) is autosar_r4p0.EcucModuleDef)
				pathreversed=tuple(path.__reversed__())
				refined=refined.select.foreach(lambda e:functools.reduce(getattr,pathreversed,e))
				dests.extend(refined)
			
			for d in dests:
				try:
					a=d.references().parent().select.findall(lambda e: isinstance(e,autosar_r4p0.Group.EcucIndexableValue))
					ret.extend(a)
				except:
					pass
		except:pass
		return ret
	def _values_EcucSymbolicNameReferenceDef(self):
		return [e for e in self._values_general() if e.definition.ref().symbolicNameValue.val()==True]
	def _values_EcucForeignReferenceDef(self):
		from . import autosar_r4p0
		desttypexml=self._definition.destinationType.val()
		group=autosar_r4p0.EcucReferenceValue.value._desttype._dests.get(desttypexml,None)
		return list(group.instances())
	def _values_EcucInstanceReferenceDef(self):
		from . import autosar_r4p0
		#InstanceRef()
		desttypexml=self._definition.destinationType.val()
		group=autosar_r4p0.EcucInstanceReferenceValue.value._desttype.target._desttype._dests.get(desttypexml,None)
		dests=set()
		dests.update(group.instances())
		contexttypexml=self._definition.destinationContext.val()
		group=autosar_r4p0.EcucInstanceReferenceValue.value._desttype.contextElement._desttype._dests.get(contexttypexml,None)
		contexts=set()
		contexts.update(group.instances())
		ret=[]
		for context in contexts:
			isOfType=context.isOfType().model().__repr__()
			for dest in dests:
				if dest.__repr__().startswith(isOfType):
					ret.append(InstanceRef([context,],dest))
		return ret
	def __init__(self,instance,definition):
		from . import autosar_r4p0
		self._instance=instance
		if isinstance(definition,autosar_r4p0.Group.EcucContainerDef):
			self._element=instance.__class__.container if isinstance(instance,autosar_r4p0.EcucModuleConfigurationValues) else instance.__class__.subContainer
		elif isinstance(definition,autosar_r4p0.Group.EcucParameterDef):
			self._element=instance.__class__.parameterValue
		elif isinstance(definition,autosar_r4p0.Group.EcucAbstractReferenceDef):
			self._element=instance.__class__.referenceValue
		self._definition=definition
		if isinstance(definition,autosar_r4p0.Group.EcucContainerDef):
			if definition.requiresIndex.val():
				def generator():
					ret=sorted((child for child in getattr(self._instance,self._element._name) if child.definition.ref() is self._definition),key=lambda e:e.index.val() if e.index else 0xFFFFFFFF)
					for e in ret:
						yield e
			else:
				def generator():
					ret=sorted((child for child in getattr(self._instance,self._element._name) if child.definition.ref() is definition),key=lambda e:e.shortName.val())
					for e in ret:
						yield e
		else:
			if definition.requiresIndex.val():
				def generator():
					ret=sorted((child for child in getattr(self._instance,self._element._name) if child.definition.ref() is self._definition and child.value),key=lambda e:e.index.val() if e.index else 0xFFFFFFFF)
					for e in ret:
						yield e
			else:
				def generator():
					ret=sorted((child for child in getattr(self._instance,self._element._name) if child.definition.ref() is definition and child.value),key=lambda e:e.val())
					for e in ret:
						yield e

		ModelList.__init__(self,generator)
		if self.isProperty():
			deftype=type(self._definition)
			if isinstance(definition,autosar_r4p0.Group.EcucAbstractReferenceDef):
				if deftype is autosar_r4p0.EcucForeignReferenceDef:
					values=self._values_EcucForeignReferenceDef
				elif deftype is autosar_r4p0.EcucReferenceDef:
					values=self._values_general
				elif deftype is autosar_r4p0.EcucSymbolicNameReferenceDef:
					values=self._values_EcucSymbolicNameReferenceDef
				elif deftype is autosar_r4p0.EcucChoiceReferenceDef:
					values=self._values_general
				elif deftype is autosar_r4p0.EcucInstanceReferenceDef:
					values=self._values_EcucInstanceReferenceDef
				elif deftype is autosar_r4p0.EcucUriReferenceDef:
					print(f'warning: EcucUriReferenceDef at {self._definition} not supported by AutoMAT')
					values=[]
				else:
					raise AssertionError('Automat error')
				self.values=values
				def valuetype():
					return str
			else:
				if deftype is autosar_r4p0.EcucAddInfoParamDef:
					# special case, does not have a default value property, should be treated like a numeric value
					def valuetype():
						return str
					def defaultValue(self):
						return ''
				else:
					#if definition.defaultValue != None:
					def defaultValue(self):
						return self._definition.defaultValue.val()
					if deftype is autosar_r4p0.EcucBooleanParamDef:
						def valuetype():
							return bool
						def values():
							return ['true','false']
						self.values=values
					elif deftype is autosar_r4p0.EcucIntegerParamDef:
						def valuetype():
							return int
					elif deftype is autosar_r4p0.EcucFloatParamDef:
						def valuetype():
							return float
					elif deftype in (autosar_r4p0.EcucEnumerationParamDef,autosar_r4p0.EcucFunctionNameDef,autosar_r4p0.EcucLinkerSymbolDef,autosar_r4p0.EcucMultilineStringParamDef,autosar_r4p0.EcucStringParamDef):
						def valuetype():
							return str
						if deftype is autosar_r4p0.EcucEnumerationParamDef:
							def values(self):
								return list((name.val() for name in self._definition.literal.shortName))
							self.values=types.MethodType(values, self)
						else:
							if deftype is autosar_r4p0.EcucLinkerSymbolDef:
								# see TPS_ECUC_02030
								def regex():
									return '[a-zA-Z][a-zA-Z0-9_.$%]{0,254}'
								self.regex=regex
							elif deftype is autosar_r4p0.EcucFunctionNameDef:
								# see TPS_ECUC_06075
								def regex():
									return '[a-zA-Z_][a-zA-Z0-9_]*'
								self.regex=regex
							elif self._definition.regularExpression!=None:
								def regex(self):
									return self._definition.regularExpression.val()
								self.regex=types.MethodType(regex, self)
					else:
						raise AssertionError('Automat error')
				self.defaultValue=types.MethodType(defaultValue, self)
			self.valuetype=valuetype
	def __dir__(self):
		return list(e for e in object.__dir__(self) if e[0]!='_')
	def __getattr__(self,val):
		raise AttributeError()
	def __bool__(self):
		if self.isProperty():
			for child in getattr(self._instance,self._element._name):
				if child.definition.ref() == self._definition and child.value:
					return True
			return False
		else:
			return self._definition in getattr(self._instance,self._element._name).definition.ref()
	def validate(self):
		assert self._definition!=None,'missing or not loaded module definition'
		if self.isProperty():
			mult=sum(1 for child in getattr(self._instance,self._element._name) if child.definition.ref() is self._definition and child.value)
		else:
			mult=sum(1 for child in getattr(self._instance,self._element._name) if child.definition.ref() is self._definition)
		from .autosar_r4p0 import Group
		assert self.lowerMultiplicity()<=mult or \
			self.lowerMultiplicity() == 1 and hasattr(self._definition,'defaultValue') and self._definition.defaultValue or \
			isinstance(self._definition, Group.EcucParameterDef) and self._definition.withAuto.val(), 'multiplicity low'
		if mult>self.upperMultiplicity():
			if self.upperMultiplicity() > 1:
				assert False, 'multiplicity to high'
			else:
				assert False, 'Value specified multiple times: ' + str(self.select.foreach(lambda e:e.file_line())) 
		if self._definition._validationCondGrouped:
			self._definition._validationCondGrouped(self)


class DefaultValueDefinition:
	def __init__(self, parent, definition):
		self._parent = parent
		self._def = definition
	def val(self):
		return str(self._def)
	def ref(self):
		return self._def
	def parent(self):
		return self._parent
	def __repr__(self):
		return repr(self._parent) + '.definition'


class DefaultValueType():
	def __init__(self,parent,definition):
		self._parent=parent
		self._def=definition
	def val(self):
		return self._def.defaultValue.val()
	def parent(self):
		return self._parent
	def module(self):
		return self._parent.module()
	@property
	def definition(self):
		return DefaultValueDefinition(self, self._def)
	def validate(self):
		assert self._def,'Definition is missing'
		self._def._validate(self)
		for vc in self._def._validationConds:
			vc(self)
	def editor(self,*args,**kwargs):
		name=self._def.shortName.val()
		setattr(self._parent.editor(*args,**kwargs),name,self.val())
		return getattr(self._parent,name).editor(*args,**kwargs)
	def model(self):
		return self
	def __repr__(self):
		return repr(self._parent)+'.'+self._def.shortName.val()
	def __str__(self):
		return str(self._parent)+'/'+self._def.shortName.val()
	@property
	def __doc__(self):
		return str(self._def.desc.l2[0].val())

class ElementList(ModelList):
	_selected=None
	def __dir__(self):
		ret=ModelList.__dir__(self)
		if self._selected is None and type(self._element._desttype) is dict and len(self._element._desttype) > 1:
			ret.extend((e.__name__ for e in self._element._desttype.values()))
		#if self._element._numInstancesMax==1 and (self._selected is None or len(self)==1 and type(self[0]) is self._selected):
		#	ret.extend(dir(self[0]))
		return ret
	@property
	def __doc__(self):
		return 'List of all objects in '+self._element._name+('' if self._selected is None else 'of type '+self._selected.__name__)
	def __init__(self,generator,instance,element):
		super().__init__(generator)
		self._instance=instance
		self._element=element
	def __getattr__(self,val):
		if self._selected is None and type(self._element._desttype) is dict:
			subtype=next(iter(e for e in self._element._desttype.values() if e.__name__==val),None)
			if subtype is not None:
				def generator():
					for e in self._g():
						if type(e) is subtype:
							yield e
				ret=ElementList(generator,self._instance,self._element)
				ret._selected=subtype
				return ret
		#if self._element._numInstancesMax==1 and (self._selected is None or len(self)==1 and type(self[0]) is self._selected):
		#		return getattr(self[0],val)
		return ModelList.__getattr__(self,val)
	def binding(self):
		return Aggregation(self._instance, self._element)
	def validate(self):
		return self.binding().validate()

class EcucList(ElementList):
	def __dir__(self):
		ret=ModelList.__dir__(self)
		return ret
	def __init__(self,generator,instance,definition):
		ModelList.__init__(self,generator)
		self._instance=instance
		self._definition=definition
	@property
	def __doc__(self):
		return super().__doc__
	def __getattr__(self,val):
		if self._definition.upperMultiplicity.val()==1 and len(self)==1:
			return getattr(next(self.__iter__()),val)
		return ModelList.__getattr__(self,val)
	def __getitem__(self,key):
		vals=tuple(self.__iter__())
		if len(vals) == 0:
			raise IndexError(f'Cannot get item from empty list at {self}')
		if isinstance(key,int):
			return vals[key]
		elif isinstance(key,slice):
			vals=vals[key]
			def generator():
				for val in vals:
					yield val
			return EcucList(generator, self._instance, self._definition)
		else:
			from .autosar_r4p0 import Group
			return next((i for i in vals if isinstance(i,Group.Referrable) and i.shortName.val()==key),ModelNone)
	def binding(self):
		return EcucAggregation(self._instance,self._definition)
		
class InstanceRefList(ElementList):
	@property
	def __doc__(self):
		return super().__doc__
	def __getattr__(self,val):
		if self._selected is None and type(self._element._desttype) is dict:
			subtype=next(iter(e for e in self._element._desttype.values() if e.__name__==val),None)
			if subtype is not None:
				def generator():
					for e in self._g():
						if type(e.model()) is subtype:
							yield e
				ret=ElementList(generator,self._instance,self._element)
				ret._selected=subtype
				return ret
		#if self._element._numInstancesMax==1 and (self._selected is None or len(self)==1 and type(self[0]) is self._selected):
		#		return getattr(self[0],val)
		return ModelList.__getattr__(self,val)
	
class Element(object):
	_numInstancesMax=1
	__slots__=('_owner','_parenttype','_name','_property','_numInstancesMin','_splitable','_splitkey','_xmlname','_desttype','_childname','__doc__')
	def _setup(self,parent):
		child=parent.__dict__[self._childname]
		if child is not ModelNone:
			child._setup()
	def _unsetup(self,parent):
		child=parent.__dict__[self._childname]
		if child!=ModelNone:
			child._unsetup()
	def __init__(self,parenttype,name,_property,numInstancesMin,splitable,splitkey,xmlname,desttype,childname,doc):
		self._parenttype=parenttype
		self._name=name
		self._property=_property
		self._numInstancesMin=numInstancesMin
		self._splitable	 =splitable	 
		self._splitkey	   =splitkey
		self._xmlname		=xmlname
		self._desttype	   =desttype
		self._childname=childname
		self.__doc__=doc
	def _setxml(self,instance,xml):
		childobj=self._desttype()
		try:
			instance.__dict__[self._childname]=childobj # must be set to be able to get path
			childobj._setxml(xml,self,instance)
		except:
			instance.__dict__[self._childname]=ModelNone
			print(f'Invalid content {xml.text} at {instance.__str__()}/{self._name} file {xml.base} line {xml.sourceline}, object will be deleted when file saved')
			import AutoMAT
			AutoMAT.files.modified(xml.base)
			xml.getparent().remove(xml)
	def _appendxml(self,to,other):
		otherval=other.__dict__[self._childname]
		if not otherval:
			return
		otherval._parent=to
		toval=to.__dict__[self._childname]
		if not toval:
			to.__dict__[self._childname]=otherval
			otherval._setup()
			return
		toval._appendxml(otherval)
	def _merge(self,to,other):
		otherval=other.__dict__[self._childname]
		if not otherval:
			return
		otherval._parent=to
		toval=to.__dict__[self._childname]
		if not toval:
			to.__dict__[self._childname]=otherval
			otherval._setup()
			return
		if not self._splitable:
			print(f'Warning: Trying to merge unmergeable content at {toval.__repr__()} from files {[otherval._xml[0].base,*(xml.base for xml in toval._xml)]}')
		toval._merge(otherval)
	def __get__(self, instance, owner):
		if instance is None:
			self._owner=owner
			return self
		return instance.__dict__[self._childname]
	def __set__(self,instance,value):
		raise AttributeError() # to make it a data descriptor

class Elements(object):
	__slots__=('_owner','_parenttype','_name','_property','_numInstancesMin','_splitable','_splitkey','_isOrdered','_numInstancesMax','_xmlname','_desttype','_childname','__doc__')
	def __init__(self,parenttype,name,_property,numInstancesMin,splitable,splitkey,isOrdered,numInstancesMax,xmlname,childtypes,childname,doc):
		self._parenttype=parenttype
		self._name=name
		self._property=_property
		self._numInstancesMin=numInstancesMin
		self._splitable=splitable
		self._splitkey=splitkey
		self._isOrdered=isOrdered
		self._numInstancesMax=numInstancesMax
		self._xmlname=xmlname
		self._desttype=childtypes
		self._childname=childname
		self.__doc__=doc
	def _setup(self,parent):
		children=parent.__dict__[self._childname]
		for child in children:
			child._setup()
	def _unsetup(self,parent):
		children=parent.__dict__[self._childname]
		for child in children:
			child._unsetup()
	def __get__(self, instance, owner):
		if instance is None:
			self._owner=owner
			return self
		name='_ElementList'+self._childname
		if name not in instance.__dict__:
			def generator():
				elist=instance.__dict__[self._childname]
				for i in elist:
					yield i
			generator.__doc__=self.__doc__
			instance.__dict__[name]=ElementList(generator,instance,self)
		return instance.__dict__[name]
	def __set__(self,instance,value):
		raise AttributeError() # to make it a data descriptor
	def _appendxml(self,to,other):
		otherval=other.__dict__[self._childname]
		if not otherval:
			return
		for val in otherval:
			val._parent=to
		toval=to.__dict__[self._childname]
		if not toval:
			to.__dict__[self._childname]=otherval
			for v in otherval:
				v._setup()
			return
		if len(toval) != len(otherval):
			print(f'trying to merge container list that is not mergable at {to}, content from file {other.file()[0]} will be ignored')
			return
		for t,o in zip(toval, otherval):
			t._appendxml(o)
		#assert 0,f'Trying to merge unmergeable content'
	def _sortkey(self,cont):
		if self._isOrdered:
			return ''
		# sort containers based alphabetically on split key
		key=','.join(findtext(cont._xml[0],key) for key in self._splitkey).lower()+','+type(cont).__name__
		return key
	def _sortxml(self,xml):
		for child in xml:
			if child.__class__ is not ET._Element:
				continue
			try:
				childtype=self._desttype[no_ns(child.tag)]
			except:
				xml.remove(child)
				continue
			childtype._sortxml(child)
		if not self._isOrdered and len(xml) > 1:
			def index(xml):
				try:
					key=','.join(findtext(xml,key) for key in self._splitkey).lower()+','+self._desttype[no_ns(xml.tag)].__name__
					return key
				except:
					n=xml.getnext()
					if n is None:
						return 'z'
					else:
						return index(n)
			xml[:]=sorted(xml,key=index)
	def _merge(self,to,other):
		otherval=other.__dict__[self._childname]
		if not otherval: # check if list empty
			return
		toval=to.__dict__[self._childname]
		if not toval: # check if list empty
			to.__dict__[self._childname]=otherval
			for v in otherval:
				v._parent=to
				v._setup()
			return
		if not self._splitable:
			# is this correct? The limit is even harder, it should be limited to which parent the content belongs
			print(f'Error: Trying to merge unmergeable content arrays at {to.__repr__()}.{self._name} from files {[otherval[0]._xml[0].base,*(xml.base for xml in toval[0]._xml)]}, content from {otherval[0]._xml[0].base} will be ignored')
			return
		if self._isOrdered:
			print(f'Error: Trying to merge ordered collection {to.__repr__()}.{self._name} which is not allowed according to constr_2547, content from {otherval[0]._xml[0].base} will be ignored')
			return
		from . import autosar_r4p0
		if not self._splitkey:#isinstance(toval[0], autosar_r4p0.Group.Identifiable):
			# should split key be used for the merge here?
			for v in otherval:
				v._parent=to
				v._setup()
			toval.extend(otherval)
			return
		keys=[self._sortkey(t) for t in toval]
		start=0
		length=len(keys)
		othervaliter=iter(otherval)
		for o in othervaliter:
			key=self._sortkey(o)
			o._parent=to
			start=bisect.bisect_left(keys,key,lo=start)
			if start==length:
				# end of list, add the rest at the end
				o._setup()
				toval.append(o)
				for o in othervaliter:
					o._parent=to
					o._setup()
					toval.append(o)
				break
			if keys[start]==key:
				# merge containers
				toval[start]._merge(o,self._splitkey)
			else:
				o._setup()
				toval.insert(start,o)
				keys.insert(start,key)
				length+=1
				start+=1
		if len(toval) > self._numInstancesMax:
			print(f'To many child instances (num instances:{len(toval)} max:{self._numInstancesMax}) of {self._name} in {to.__repr__()} after merge')
	def _setxml(self,instance,xml):
		children=instance.__dict__[self._childname]
		if isinstance(self._desttype,dict):
			for child in xml:
				if child.__class__ is not ET._Element or not child.tag.startswith('{http://autosar.org/schema/r4.0}'):
					continue
				tag=child.tag[len('{http://autosar.org/schema/r4.0}'):]
				if tag in self._desttype:
					assert len(children) < self._numInstancesMax, f'To many child instances ({len(children)+1} > {self._numInstancesMax}) of {instance.__str__()}/{self._name}, file {child.base}, line {child.sourceline}'
					childtype=self._desttype[tag]
					childobj=childtype()
					try:
						children.append(childobj) # must be set to be able to get path
						childobj._setxml(child,self,instance)
					except Exception as e:
						children.pop()
						print(f'Invalid content {child.text} at {instance.__str__()} ({str(e)}) file {child.base} line {child.sourceline}, object will be deleted when file saved')
						import AutoMAT
						AutoMAT.files.modified(child.base)
						child.getparent().remove(child)
		else:
			assert len(children) < self._numInstancesMax, f'To many child instances in {no_ns(xml.tag)}, file {child.base}, line {child.sourceline}'
			childobj=self._desttype()
			try:
				children.append(childobj) # must be set to be able to get path
				childobj._setxml(xml,self,instance)
			except:
				children.pop()
				print(f'Invalid content  {xml.text} at {instance.__str__()} file {xml.base} line {xml.sourceline}, object will be deleted when file saved')
				import AutoMAT
				AutoMAT.files.modified(xml.base)
				xml.getparent().remove(xml)
		if not self._isOrdered:
			children.sort(key=self._sortkey)

class Attribute:
	__slots__=('_name','_numInstancesMin','_valueType','_xmlname','_required','__doc__')
	_property=True
	_numInstancesMax=1
	def __init__(self,name,valuetype,xmlname,required,doc):
		self._name=name
		self._numInstancesMin=int(required)
		self._valueType=valuetype
		self._xmlname   =xmlname
		self._required  =required
		self.__doc__=doc
	def __get__(self, instance, owner):
		if instance is None:
			return self
		xml=instance._xml[0]
		if self._xmlname in xml.attrib:
			return self._valueType(xml.attrib[self._xmlname])
		return ModelNone
	def __set__(self,instance,value):
		raise AttributeError() # to make it a data descriptor
